/*
 * Copyright (C) 2010, Funcom AS, All Rights Reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  @authors
 *  Svein Gunnar Larsen <sveingunnarlarsen@gmail.com>
 *  Erik Thune Lund <culex.eriklund@gmail.com>
 *  Michel Krohn-Dale <kronicz@gmail.com>
 */

package com.funcom.peeler;

import com.funcom.peeler.action.Change;
import com.funcom.peeler.component.PeelComponentFactory;
import com.funcom.peeler.component.SelectionBox;
import com.funcom.peeler.peelable.*;
import com.funcom.peeler.symbol.Symbol;
import com.funcom.peeler.tool.*;
import com.funcom.peeler.utils.WorkBenchMenuBar;
import com.funcom.peeler.utils.WorkBenchToolBar;
import com.gibbon.jme.context.JmeContext;
import com.jme.input.KeyInput;
import com.jme.renderer.ColorRGBA;
import com.jmex.bui.BComponent;
import com.jmex.bui.Rule;
import org.apache.commons.io.FilenameUtils;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.*;
import java.util.List;
import java.util.concurrent.Callable;

import static com.funcom.peeler.UpdateCont.get;
import static com.funcom.peeler.utils.RelativePath.getRelativeFile;

/**
 * WorkBench class that contains an editing window, a bui context area,
 * a history of changes and a set of tools.
 */
public class WorkBench {

    private static final String banana_peeler_title = "Banana Peeler - ";
    private History history = new History( this );

    private WorkBenchFrame frame = new WorkBenchFrame();
    private Bui bui = new Bui( frame.getCanvas() );

    private boolean export = false;

    private JFileChooser chooser = new JFileChooser() {
        {
            setCurrentDirectory(new File(getBasePath()));
        }
    };

    private Map<Symbol.ToolType,Tool> tools = new HashMap<Symbol.ToolType,Tool>() {
        {
            put( Symbol.ToolType.SELECT, new SelectionBoxTool( WorkBench.this ) );
            put( Symbol.ToolType.MOVE, new MoveTool( WorkBench.this ));
            put( Symbol.ToolType.PAN, new PanTool( WorkBench.this ) );
            put( Symbol.ToolType.PLACE, new PlaceTool( WorkBench.this ) );
            put( Symbol.ToolType.MULTI, new MultiTool( WorkBench.this ) );
            put( Symbol.ToolType.RESIZE, new ResizeTool( WorkBench.this ) );
        }
    };

    private Stack<Tool> tool_stack = new Stack<Tool>();
    private Tool current_tool = tools.get( Symbol.ToolType.MULTI );
    private Tool default_tool = new DefaultTool(this);


    /**
     * Default constructor for WorkBench, creates an empty workspace
     * Adds the bui context to it's pass manager for rendering
     */
    public WorkBench() {
        get().registerWorkbench( this );
        frame.getContext().getPassManager().add( bui );
    }

    /**
     * Constructor for WorkBench to load a file upon construction
     * and populate the workspace
     * @param filename the filename of the file to be loaded
     */
    public WorkBench( final String filename ) {
        this();

        bui.execute( new Task() {
            public void run() {
                bui.loadFile( filename );
                frame.setTitle( banana_peeler_title + getBui().getPeelRoot().getSettings().getWorkingFile() );
            }
        });
    }

    /**
     * Get the stylesheet this WorkBench uses
     * @return the stylesheet that this WorkBench uses
     */
    public PeelStyleSheet getWorkBenchStyle() {
        return bui.workspace.getStyleSheet();
    }

    /**
     * Gets the base path for resources
     * @return the base path that this WorkBench uses for resources
     */
    public String getBasePath(){
        return bui.getPeelRoot().getSettings().getBasePath();
    }

    /**
     * Gets the WorkBench.Bui
     * @return the WorkBench.Bui context in this WorkBench
     */
    public Bui getBui() {
        return bui;
    }

    /**
     * Gets the WorkBenchFrame
     * @return the WorkBenchFrame in this WorkBench
     */
    public WorkBenchFrame getFrame() {
        return frame;
    }

    /**
     * Gets the WorkBenchToolBar
     * @return the WorkBenchToolBar in this WorkBench
     */
    public WorkBenchToolBar getToolBar(){
            return frame.toolBar;
    }

    /**
     * Saves the contents of the canvas window to a designated file,
     * optionally saves the style of the canvas window to file as well
     */
    public void exportCanvas() {

        export = true;
        String export_path = chooseComponentSaveFile();
        if ( export_path == null ) return;

        File f = getRelativeFile(new File(export_path), new File( getBui().getPeelRoot().getSettings().getBasePath() ) );
        String relativePath = f.getPath();

        getBui().peelroot.setStyleSheetPath( f.getName() + ".bss" );

        Set< Rule > rules = new HashSet< Rule >();
        for ( PeelComponent component : getBui().canvas_window.getChildren() ) {
            rules.addAll( StyleSheetExporter.getRelevantRules( component ) );

        }

        int export_canvas_style = JOptionPane.showConfirmDialog( frame, "Export the canvas window style?", "Export canvas window", JOptionPane.YES_NO_OPTION );
        if( export_canvas_style == JOptionPane.OK_OPTION ) {
            rules.addAll(StyleSheetExporter.getRelevantRules(getBui().canvas_window));
        }
        rules.add( getBui().getPeelRoot().getStyleSheet().properties.get( "root" ) );

        PeelerIO.writePeeledNodesToFile( getBui().canvas_window.peel(), export_path + ".xml" );
        PeelerIO.writeStyleRulesToFile( rules, export_path + ".bss" );

        System.out.println( "Saving components to " + export_path + ".xml" );
        System.out.println( "Saving stylesheet to " + export_path + ".bss" );
    }

    /**
     * Saves the workspace to a normal working file with the filename specified
     * and with a bss file right next to it. Only saves if the history has changed
     * or is otherwise save-worthy
     */
    public void saveWorkSpace() {

        if ( !history.needSave() ) return;

        export = false;

        String workingFile = getBui().getPeelRoot().getSettings().getWorkingFile();

        if ( workingFile == null ) workingFile = chooseComponentSaveFile();
        if ( workingFile == null ) return; // Still null, return

        getBui().getPeelRoot().getSettings().setWorkingFile( workingFile );

        frame.setTitle( banana_peeler_title + workingFile );

        File f = getRelativeFile(new File(workingFile), new File( getBui().getPeelRoot().getSettings().getBasePath() ) );

        getBui().peelroot.setStyleSheetPath( f.getName() + ".bss" );

        PeelerIO.writePeeledNodesToFile( getBui().peelroot.peel(), workingFile + ".xml");
        PeelerIO.writeStyleSheetToFile( getBui().peelroot.getStyleSheet(), workingFile + ".bss" );

        System.out.println( "Saving components to " + workingFile + ".xml" );
        System.out.println( "Saving stylesheet to " + workingFile + ".bss" );

        history.mark();
    }

    /**
     * Pops up the chooser used to choose what file to save to or to export to
     * Also sets the filename to b
     * @return null on cancel, the chosen file otherwise
     */
    private String chooseComponentSaveFile() {

        FileFilter xmlFilter = new ExtensionFileFilter(".xml", new String[] { "xml"});
        chooser.setFileFilter(xmlFilter);

        if(!export) chooser.setDialogTitle("Saving Workspace Area");
        else chooser.setDialogTitle("Saving Export Area");

        int result = chooser.showSaveDialog( frame );

        switch( result ) {
            case JFileChooser.APPROVE_OPTION:
                String file = chooser.getSelectedFile().getPath();
                //Remove extension if present from filename
                file = FilenameUtils.removeExtension(file);
                getBui().getPeelRoot().setLastPath( chooser.getSelectedFile().getParent() );
                return file;
        }
        return null;
    }

    public void close() {
        frame.dispatchEvent( new WindowEvent( frame, WindowEvent.WINDOW_CLOSING ));
    }

    private void deregister() {
        get().deregisterWorkBench( this );
    }

    /**
     * Push a change to the workbench for later
     * execution
     * @param c the Change to be pushed
     */
    public synchronized void pushChange( Change c ) {
        history.pushChange( c );
    }

    /**
     * Undoes one change in the WorkBench history
     */
    public void undo() {
        history.undo();
    }

    /**
     * Redoes one change in the WorkBench history
     */
    public void redo() {
        history.redo();
    }

    /* Select a new tool from the tools this workbench has
        DeSelect the old tool if the new one is different
        The resulting tool shall never be null
     */

    /**
     * Set the tool the workbench will be using now
     * Avoid setting to the same as the current tool
     * and setting the current tool to null
     * @param tool The ToolType key for the new Tool
     */
    public void updateTool( Symbol.ToolType tool ) {

        if ( tools.get( tool ) == null ) return;

        if ( tools.get( tool ) != current_tool ) {
            updateTool( tools.get( tool ) );
        }
    }

    /**
     * Sets the new Tool by deSelecting the current tool
     * and selecting the new Tool
     * @param tool The Tool to replace the current tool
     */
    private void updateTool( Tool tool ) {
        current_tool.deSelect();
        current_tool = tool;
        current_tool.select();
    }

    /**
     * Pushes the current tool onto the tool stack
     * and selects the new tool as the current
     * @param tool the Tool to be pushed onto the stack
     */
    public void pushTool( Tool tool ) {
        tool_stack.push( current_tool );
        updateTool( tool );
    }

    /**
     * Pops a Tool from the stack of tools
     * and selects the next one down. If the stack is
     * empty, does nothing
     * @return the current tool
     */
    public Tool popTool() {
        if ( !tool_stack.isEmpty() ) {
            updateTool( tool_stack.pop() );
        }
        return current_tool;
    }

    /**
     * The window frame that contains the canvas area, menu and toolbar for
     * the workbench
     */
    public class WorkBenchFrame extends JMEFrame {

        WorkBenchToolBar toolBar = new WorkBenchToolBar();

        /**
         * Constructor for the WorkBenchFrame
         * Set a nice default size, adds a menubar up at the top,
         * puts the toolbar down at the bottom
         * and sets a custom close operation.
         * Finally pack it up so it looks proper
         */
        WorkBenchFrame() {
            super( "WorkBench", 1024, 768 );
            setTitle( banana_peeler_title + "Untitled" );

            setJMenuBar( new WorkBenchMenuBar( WorkBench.this ) );

            getContentPane().add(toolBar, BorderLayout.SOUTH);

            setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
            addWindowListener( new WindowAdapter() {

                public void windowClosing( WindowEvent e ) {
                    if ( !history.needSave() ) {
                        deregister();
                        bui.clearSelections();
                        dispose();
                        return;
                    }

                    int result = JOptionPane.showConfirmDialog( frame, "Save this workspace?" );

                    switch ( result ) {

                        case JOptionPane.YES_OPTION:
                            saveWorkSpace();
                            deregister();
                            dispose();
                            break;

                        case JOptionPane.NO_OPTION:
                            deregister();
                            bui.clearSelections();
                            dispose();
                            break;
                    }
                }
            });
            pack();
        }

    }

    /**
     * The main bui context, contained within a renderpass supplied by
     * JMEContext. The context is drawn within the canvas supplied by the frame
     * during construction
     */
    public class Bui extends BuiPass {

        int zoom = 100;

        private PeelWorkSpace workspace = new PeelWorkSpace();

        private PeelCanvasWindow canvas_window;

        private PeelLayer selection_layer = new PeelLayer();
        PeelRoot peelroot = new PeelRoot( workspace, getFrame().toolBar );

        private WorkBenchListener listener = new WorkBenchListener();

        final private Map< PeelComponent, SelectionBox > selectionBoxes = Collections.synchronizedMap( new HashMap< PeelComponent, SelectionBox >() );

         /**
         * Constructor for the bui context
         * @param canvas The canvas that this context is bound to
         */
        public Bui( Canvas canvas) {
            super(canvas);
        }

        /**
         * Get the PeelRoot for this context
         * @return The PeelRoot for this context
         */
        public PeelRoot getPeelRoot() {
            return peelroot;
        }

        /**
         * Get the WorkSpace in this context
         * @return the WorkSpace in this context
         */
        public PeelWorkSpace getWorkSpace() {
            return workspace;
        }

        /**
         * Get the current zoom level in percent
         * @return The current zoom level
         */
        public int getZoom() {
            return zoom;
        }

        /**
         * Set the zoom level
         * @param zoom The new zoom level
         */
        public void setZoom( int zoom ) {

            this.zoom = zoom;
            bui.getSceneRoot().getLocalScale().set( zoom / 100.0f, zoom / 100.0f, 1 );

            if ( getZoom() > 1000 ) setZoom( 1000 );
            if ( getZoom() < 25 ) setZoom( 25 );
        }

        /**
         * Init function called by JMEContext on being added to a pass
         * Initialize the context, add listeners, intialize the workspace,
         * add canvas window, add selection layer and focuslistener
         * @param context The context the intialization is run in
         */
        public void initPass(JmeContext context) {
            super.initPass(context);

            context.getRenderer().setBackgroundColor( ColorRGBA.white );

            sceneCanvas.addMouseListener(listener);
            sceneCanvas.addMouseMotionListener(listener);
            sceneCanvas.addMouseWheelListener( listener );
            sceneCanvas.addKeyListener(listener);
            sceneCanvas.addKeyListener( (KeyListener) KeyInput.get() );
            sceneCanvas.addFocusListener(listener);

            workspace.getComponent().setStyleSheet(peelroot.getStyleSheet());

            initWorkSpace( workspace );

            canvas_window = (PeelCanvasWindow)PeelComponentFactory.makePeelComponent( Symbol.BType.CANVASWINDOW ) ;
            workspace.addChild( canvas_window );

            selection_layer.getComponent().setStyleSheet( workspace.getStyleSheet() );
            sceneRoot.addWindow( selection_layer.getComponent() );

            sceneCanvas.addFocusListener( new FocusListener() {
                public void focusGained( FocusEvent e ) {
                    get().setFocusedWorkBench( WorkBench.this );
                }

                public void focusLost( FocusEvent e ) {
                }
            });
            sceneCanvas.requestFocus(); // Trigger the focuslistener and clear PropertiesBox with the nullpointer
        }

        /**
         * Update function called by JMEContext
         * Performs all Tasks waiting to be executed
         * performs any changes waiting to be performed
         * @param context the context the update is run in
         */
        public void doUpdate(JmeContext context) {
            super.doUpdate(context);
            history.doPendingChanges();
        }

        /**
         * Intializes the new workspace window into the scene root node and centers
         * it on the canvas
         * @param new_workspace the workspace to be intialized
         */
        private void initWorkSpace( PeelWorkSpace new_workspace ) {

            if ( workspace != null && workspace.getComponent().getRootNode() == sceneRoot )
                sceneRoot.removeWindow( workspace.getComponent() );

            sceneRoot.addWindow( new_workspace.getComponent() );

            // Center the workspace on creation
            new_workspace.getComponent().setLocation( (sceneCanvas.getWidth() - new_workspace.getComponent().getWidth())/2,
                                                        (sceneCanvas.getHeight() - new_workspace.getComponent().getHeight())/2 );

            workspace = new_workspace;
        }

        /**
         * Loads a file into this workspace and replaces the contents
         * with the contents of the file that's loaded. Takes care of
         * replacing the canvas window and workspace with the loaded
         * one or creating new ones to fit components in the case of
         * a loaded exported file( has no canvas window or workspace )
         * @param filename The name of the file to be loaded
         */
        private void loadFile( String filename ) {

            workspace.removeChild( canvas_window );
            peelroot.unpeel( PeelerIO.readPeeledNodes( filename ) );

            if ( peelroot.getRootWindow() instanceof PeelWorkSpace ) {
                initWorkSpace( (PeelWorkSpace)peelroot.getRootWindow() );
            }

            // Refresh selection layer at top
            sceneRoot.removeWindow( selection_layer.getComponent() );
            sceneRoot.addWindow( selection_layer.getComponent(), true );

            canvas_window = workspace.getCanvas();

            // Only null on imported exportations, compensate by making a new workspace
            if ( canvas_window == null ) {

                // Transfer all children from the new workspace to the new canvas window
                canvas_window = (PeelCanvasWindow) PeelComponentFactory.makePeelComponent( Symbol.BType.CANVASWINDOW );
                List< PeelComponent > children = new LinkedList< PeelComponent >(  workspace.getChildren() );

                canvas_window.getComponent().resizeToChildren( children );

                for ( PeelComponent component : children ) {
                    workspace.removeChild( component );
                    canvas_window.addChild( component );
                }

                // Finally add the newly populated canvas_window
                workspace.addChild( canvas_window );
            }

            getPeelRoot().getSettings().setWorkingFile( FilenameUtils.removeExtension( filename ) );
            getBui().getPeelRoot().setLastPath( filename );
        }

        /**
         * Gets the PeelComponent hit by the specified MouseEvent
         * @param e the MouseEvent to check on
         * @return the hit PeelComponent
         */
        public PeelComponent getHitComponent( MouseEvent e ) {

            BComponent bComponent = workspace.getComponent().getHitComponent( e.getX(), e.getY() );
            return peelroot.getPeelComponent( bComponent );
        }

        /**
         * Selects a PeelComponent
         * Creates a selection box on the component and
         * adds it to the selection list
         * @param new_select the PeelComponent to select
         */
        public synchronized void select( PeelComponent new_select ) {

            // if the new_select is null or otherwise invalid for selection, return
            if ( new_select == null ) return;
            if ( new_select instanceof PeelWorkSpace ) return;

            // if the new_select is a child of a previously selected component, it shall not be selected, return
            for( PeelComponent component : getSelections() ) {
                if( new_select.getParent() == component ) return;
            }

            // if the new_select is a parent, it shall deselect all it's children
            if ( new_select instanceof PeelContainer ) {
                for ( PeelComponent child : ((PeelContainer)new_select).getChildren() ) {
                    deselect( child );
                }

                SelectionBox box = new SelectionBox( new_select );
                selection_layer.getComponent().add( box );
                selectionBoxes.put( new_select, box );
            }

            // Previously selected components are not to be selected again
            if ( selectionBoxes.containsKey( new_select ) ) return;

            SelectionBox box = new SelectionBox( new_select );
            selection_layer.getComponent().add( box );
            selectionBoxes.put( new_select, box );

            // Select all components in the same group as the selected component
            if ( new_select.hasGroup() ) {
                for ( PeelComponent groupie : new_select.getGroup() ) {
                    select( groupie );
                }
            }
        }

        /**
         * Deselects a PeelComponent
         * Removes the selection box from a PeelComponent
         * and deselects any group members in the component's
         * group
         * @param component
         */
        public synchronized void deselect( PeelComponent component ) {
            if ( !selectionBoxes.containsKey( component ) ) return;

            SelectionBox box = selectionBoxes.get( component );
            selection_layer.getComponent().remove( box );
            selectionBoxes.remove( component );

            if ( component.hasGroup() ) {
                for ( PeelComponent groupie : component.getGroup() ) {
                    deselect( groupie );
                }
            }
        }

        /**
         * Clears all the selections on the WorkBench
         */
        public synchronized void clearSelections() {
            for ( PeelComponent component : getSelections() ) {
                deselect( component );
            }
        }

        /**
         * Gets all the selections on the WorkBench
         * @return a Set of PeelComponents that contains all the selections
         */
        public synchronized Set<PeelComponent> getSelections() {
            return new HashSet<PeelComponent>( selectionBoxes.keySet() );
        }

        /**
         * Gets all the PeelComponents from the PeelRoot
         * @return a Set of PeelComponents
         */
        public Set< PeelComponent> getAllComponents() {
            return peelroot.getAllComponents();
        }

        /**
         * Gets the selection layer in the WorkBench
         * @return the PeelLayer that is the selection layer
         */
        public PeelLayer getSelectionLayer() {
            return selection_layer;
        }

        /**
         * Listener class that recieves all events destined for the canvas
         * and passes them on to the default tool and the selected tool
         */
        private class WorkBenchListener implements MouseListener, MouseMotionListener, MouseWheelListener, KeyListener,
                                                   FocusListener {

            /**
             * Translates mouse coords to workspace coords
             * Reverse y-axis, scale coords according to zoom level
             * @param e the coords to be translated
             */
            private void fixMouseCoords( MouseEvent e ) {
                e.translatePoint( 0, sceneCanvas.getHeight() - 2 * e.getY() );
                e.translatePoint( (int)(e.getX() / sceneRoot.getLocalScale().getX() - e.getX()),
                                    (int)(e.getY() / sceneRoot.getLocalScale().getY() - e.getY()) );
            }

            /**
             * Workaround for windows java autorepeat, stores the last typed
             * character keycode
             */
            int last_typed;

            /**
             * Passes on the KeyEvent to the tools
             * @param e the KeyEvent to be passed on
             */
            public void keyTyped( KeyEvent e ) {
                default_tool.keyTyped( e );
                if ( current_tool != null ) current_tool.keyTyped( e );
            }

            /**
             * Discard the keypress if the incoming one has the same keycode
             * as the the one already held in.
             * Passes on the KeyEvent to the tools
             * @param e the KeyEvent to be passed on
             */
            public void keyPressed( KeyEvent e ) {
                if(last_typed == e.getKeyCode()) { return;}
                default_tool.keyPressed( e );
                if ( current_tool != null ) current_tool.keyPressed( e );
                last_typed = e.getKeyCode();
            }

            /**
             * Passes on the KeyEvent to the tools
             * Resets the last typed key in preparation for new key presses
             * @param e the KeyEvent to be passed on
             */
            public void keyReleased( KeyEvent e ) {
                default_tool.keyReleased( e );
                if ( current_tool != null ) current_tool.keyReleased( e );
                last_typed = 0;
            }

            /**
             * Translates the MouseEvent and passes it on
             * to the tools
             * @param e the MouseEvent to pass on
             */
            public void mouseClicked(MouseEvent e) {
                fixMouseCoords( e );
                default_tool.mouseClicked( e );
                if ( current_tool != null ) current_tool.mouseClicked( e );
            }

            /**
             * Translates the MouseEvent and passes it on
             * to the tools
             * @param e the MouseEvent to pass on
             */
            public void mousePressed(MouseEvent e) {
                fixMouseCoords( e );
                default_tool.mousePressed( e );
                if ( current_tool != null ) current_tool.mousePressed( e );
            }

            /**
             * Translates the MouseEvent and passes it on
             * to the tools
             * @param e the MouseEvent to pass on
             */
            public void mouseReleased(MouseEvent e) {
                fixMouseCoords( e );
                default_tool.mouseReleased( e );
                if ( current_tool != null ) current_tool.mouseReleased( e );
            }

            /**
             * Translates the MouseEvent and passes it on
             * to the tools
             * @param e the MouseEvent to pass on
             */
            public void mouseEntered(MouseEvent e) {
                fixMouseCoords( e );
                default_tool.mouseEntered( e );
                if ( current_tool != null ) current_tool.mouseEntered( e );
            }

            /**
             * Translates the MouseEvent and passes it on
             * to the tools
             * @param e the MouseEvent to pass on
             */
            public void mouseExited(MouseEvent e) {
                fixMouseCoords( e );
                default_tool.mouseExited( e );
                if ( current_tool != null ) current_tool.mouseExited( e );
            }

            /**
             * Translates the MouseEvent and passes it on
             * to the tools
             * @param e the MouseEvent to pass on
             */
            public void mouseDragged(MouseEvent e) {
                fixMouseCoords( e );
                default_tool.mouseDragged( e );
                if ( current_tool != null ) current_tool.mouseDragged( e );
            }

            /**
             * Translates the MouseEvent and passes it on
             * to the tools
             * @param e the MouseEvent to pass on
             */
            public void mouseMoved(MouseEvent e) {
                fixMouseCoords( e );
                default_tool.mouseMoved( e );
                if ( current_tool != null ) current_tool.mouseMoved( e );
            }

            /**
             * Translates the MouseEvent and passes it on
             * to the tools
             * @param e the MouseEvent to pass on
             */
            public void mouseWheelMoved( MouseWheelEvent e ) {
                fixMouseCoords( e );
                default_tool.mouseWheelMoved( e );
                if ( current_tool != null ) current_tool.mouseWheelMoved( e );
            }

            /**
             * Passes on the FocusEvent to the tools
             * @param e the FocusEvent to be passed on
             */
            public void focusGained(FocusEvent e) {
                default_tool.focusGained( e );
                if ( current_tool != null ) current_tool.focusGained( e );
            }

            /**
             * Passes on the FocusEvent to the tool
             * @param e the FocusEvent to be passed on
             */
            public void focusLost(FocusEvent e) {
                default_tool.focusLost( e );
                if ( current_tool != null ) current_tool.focusLost( e );
            }
        }
    }

    /**
     * Generic Task class that's used to queue actions for execution
     */
    public static abstract class Task implements Callable<Object> {

        /**
         * Override run() instead
         * @return
         */
        public Object call() {
            run();
            return null;
        }

        /**
         * Convenience method to override, easier to write out than call()
         */
        public abstract void run();
    }
} // End class WorkBench
